#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define LALEL "$"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_MAX 32
#define EXIT_CODE 33

#define IN_RDIR     0
#define OUT_RDIR    1
#define APPEND_RDIR 2

int last_code=0;
int quit=0;
char commandline[LINE_SIZE];
char* argv[ARGC_MAX];
char pwd[LINE_SIZE];
char *rdirfilename=NULL;
int rdir=0;

//在shell需要维护
//自定义环境变量表
//自定义本地变量表（一段缓冲区）

char myenv[LINE_SIZE];
//也可以malloc空间
//或者弄成一个二维数组

const char*getusername()
{
    return getenv("USER");
}

const char*gethostname()
{
    return getenv("HOSTNAME");
}

void getpwd()
{
    getcwd(pwd,sizeof(pwd));
}

void checkredir(char*cmd)
{
    //> < >>
    char *pos=cmd;
    while(*pos)
    {
        if(*pos=='>')
        {
            if(*(pos+1)=='>')
            {
                *pos++='\0';
                *pos++='\0';
                while(isspace(*pos))pos++;
                rdirfilename=pos;
                rdir=APPEND_RDIR;
                break;
            }
            else{
                *pos++='\0';
                while(isspace(*pos))pos++;
                rdirfilename=pos;
                rdir=OUT_RDIR;
                break;
            }
        }
        else if(*pos=='<')
        {
            *pos++='\0';
            while(isspace(*pos))pos++;
            rdirfilename=pos;
            rdir=IN_RDIR;
            break;
        }
        else{
            //nothing
        }
        pos++;
    }
}

void interact(char*cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LALEL" ",getusername(),gethostname(),pwd);
    //输入最好不要用scanf，因为它是以空格结尾
    //fgets可以读取一行，读取成功就为读取字符串的地址，失败返回NULL
   char*s=fgets(cline,size,stdin);
   //sizeof是否需要-1呢
   //不需要，-1是防御性编程，防止\0不够放了。后面要用到系统级别的io就要考虑是否-1
   //fgets不可能为null，不输入，按了回车之后，也算是有字符。
   //输入了命令，一敲回车就会换行，但是我们不想要换行。
   assert(s!=NULL);
   //assert在debug的情况下起效果
   (void)s;//抵消编译器的报警
   //"abcd\n\0"
   cline[strlen(cline)-1]='\0';

   checkredir(cline);
}

int splitstring(char*_argv[],char*cline)
{
    int i=0;
    _argv[i++]=strtok(cline,DELIM);
    while(_argv[i++]=strtok(NULL,DELIM));
    return i-1;
}

void normalexcute(char*_argv[])
{
     pid_t id=fork();
     if(id<0)
     {
         perror("fork fail!");
         return;
     }
     else if(id==0)
     {
         int fd=0;
        if(rdir==IN_RDIR)
        {
           fd=open(rdirfilename,O_RDONLY);
           dup2(fd,0);
        }
        else if(rdir==OUT_RDIR)
        {
           fd=open(rdirfilename,O_CREAT|O_WRONLY|O_TRUNC,0666);
           dup2(fd,1);
        }
        else
        { 
           fd=open(rdirfilename,O_CREAT|O_WRONLY|O_APPEND,0666);
           dup2(fd,1);
        }
        execvp(_argv[0],_argv);
        exit(EXIT_CODE);
        //替换失败我们就可以拿到子进程的退出信息。
        //cd echo等内建命令不能由子进程执行
        //子进程cd 与父进程无关！
     }
     else
     {
         int status=0;
         pid_t rid=waitpid(id,&status,0);
         if(rid==id)
         {
             last_code=WEXITSTATUS(status);                
         }
     }
            
}

int buildcommand(char*_argv[],int _argc)
{
     if(_argc==2&&strcmp(_argv[0],"cd")==0)
     {
         chdir(_argv[1]);
         getpwd();
         sprintf(getenv("PWD"),"%s",pwd);
         return 1;
     }
     //export也是内建命令
     //如果让子进程执行export，那么子进程的环境变量会添加，
     //但是与父进程无关，export导环境变量是给调用的进程导入。
     else if(_argc==2&&strcmp(_argv[0],"export")==0)
     {
         strcpy(myenv,_argv[1]);
         putenv(myenv);
         //只是修改了一个环境变量表的指针，让该指针指向了环境变量。
         //但是环境变量是在argv里面的，所以，下一次argv的就会改变，
         //所以环境变量指针指向的内容也会改变
         //没有直接拷贝到系统的环境变量表中
         //而是把argv里面的信息导入了，argv指向的内容一直都会变化。
         //所以我们需要一个不变的区域。
         return 1;
     }
     else if(_argc==2&&strcmp(_argv[0],"echo")==0)
     {
         if(strcmp(_argv[1],"$?")==0)
         {
             printf("%d\n",last_code);
             last_code=0;
         }
         else if(*_argv[1]=='$')
         {
             char*val=getenv(_argv[1]+1);
             if(val) printf("%s\n",val);
         }
         else{
             printf("%s\n",_argv[1]);
         }
         return 1;
     }

     //给文件加颜色！
     if(strcmp(_argv[0],"ls")==0)
     {
         _argv[_argc++]="--color";
         _argv[_argc]=NULL;
     }
     return 0;
}

int main()
{
    while(!quit)
    {
        //1.
        rdirfilename=NULL;
        rdir=0;
        //2.交互问题，获取命令行
        // ls -a -l > myfile.txt / ls -a -l >> myfile.txt / cat < myfile.txt
        interact(commandline,sizeof(commandline));

        //printf("echo :%s\n",commandline);
        //字符串分割 要有每个字串的地址 "ls -a -l" -> "ls" "-a" "-l"
        //保存在agrv（字符串数组里）
        //分割字串strtok 调用一次只能切割一个子串
        
        //3.字符串分割问题，解析命令行
        int argc=splitstring(argv,commandline);
        if(argc==0)continue;
        // for(int i=0;argv[i];i++)
        // {
        //     printf("[%d]:%s\n",i,argv[i]);
        // }
        
        //4.指令的判断
        //判断是否是内建命令，如果是就要让父进程执行相应的指令
        //内建命令本质就是shell内部的一个函数
        int n=buildcommand(argv,argc);
        
        //5.普通命令的执行
        if(!n)normalexcute(argv);

    }
    
    return 0;
}
